mybatis解析

您所在的位置:网站首页 mybatis resultmap collection原理 mybatis解析

mybatis解析

2024-07-09 17:36| 来源: 网络整理| 查看: 265

本文基于mybatis-spring 1.3.1和mybatis 3.4.4版本

mybatis中的标签association主要用于解决“有一个”类型的关系,它表示一个对象至多有一个关联对象。比如一般情况下,每个人都一个身份证,可能有些人没有(比如黑户和婴儿),人和身份证的关系就可以使用association表示。 本文接下来介绍association的三种用法,然后介绍其实现原理。

一、使用association

先定义两个类,一个是Person,一个是IDCard,分别表示人和身份证。

public class Person{ private int id; private String name; private String sex; private Date birthday; private IdCard card;//IdCard类为Person的属性 //get和set方法省略 } public class IdCard{ private int id; private String number; private Date expiredTime; //get和set方法省略 }

这两个类分别对应了两个表:

CREATE TABLE `person` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(10) NOT NULL, `sex` varchar(2) DEFAULT NULL, `birthday` date DEFAULT NULL, `card_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ); CREATE TABLE `id_card` ( `id` int(11) NOT NULL AUTO_INCREMENT, `number` varchar(255) DEFAULT NULL, `expiredTime` date DEFAULT NULL, PRIMARY KEY (`id`) );

以如下SQL语句做介绍:

select * from person p,id_card card where p.card_id=card.id and name='张三';

上面这个SQL语句一次性查询出了Person和IdCard两个对象的数据,我想让mybatis返回一个Person对象,并且Person对象里面组装好了IdCard对象,现在来看看如何使用标签association达成这个目标。

方法一 在resultMap标签里面嵌套association标签,resultMap表示Person对象,association表示IdCard对象。

select p.*,card.id as cid,card.number as number,card.expiredTime expiredTime from person p,id_card card where p.card_id=card.id and name=#{name,jdbcType=VARCHAR}

执行findPersonByName可以得到一个Person对象,且Person对象关联上了对应的IdCard对象。 注意association子标签里面的column属性不能与resultMap里面的column属性有相同的值,否则造成association的对象的属性值赋值错误。而且association标签里面的column属性不是必须的,对于上面的例子也就是column="card_id" 是可以去掉的。 方法二 如果采用方法一,当有另外一个resultMap也想嵌套相同的association时,那么需要在内部再定义一次,这会造成重复,因此可以将相同的association提取出来,写成如下形式:

方法三 方法一和方法二都是一次将所有的属性查询出来,但是可能程序中只需要Person,不需要IdCard,那有没有一种方法可以在需要的时候才查询IdCard? 答案是有。这也是association的第三个应用:关联的嵌套 Select 查询。方法一和方法二叫做关联的嵌套结果映射。

select * from person where name=#{name,jdbcType=VARCHAR} SELECT * FROM id_card WHERE id = #{id,jdbcType=INTEGER}

关联的嵌套 Select 查询是在resultMap嵌入一个association,与方法一不同的是,这里多了三个字段:column,select和fetchType。

select:加载关联对象的映射语句ID,当需要加载关联对象时,mybatis调用该属性指定的映射语句,从数据库查询出结果,并映射为关联对象;column:数据库的列名或者别名,用于指定输入列,select属性映射的SQL语句一般需要参数,比如id,这个参数就等于输入列的值,也就是映射语句根据column指定的输入列进行查询,就上例来说,association执行是SQL语句相当于:SELECT * FROM id_card WHERE id = card_id,如果需要指定多个参数,column可以写成:column="{prop1=col1,prop2=col2}",其中prop1,prop2是association执行是SQL语句中的参数名;fetchType:可选的。有效值为 lazy 和 eager。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled,默认是eager。该属性表示是否将关联SQL一起执行,lazy表示需要关联对象的时候再执行,这也称作延迟加载或者懒加载。

fetchType需要和aggressiveLazyLoading联合使用,aggressiveLazyLoading是在mybatis的配置文件中配置,有两个值:true和false,在3.4.1及之前的版本中默认为 true。当该属性为true时,调用findPersonByName方法,mybatis不会执行任何SQL,如果访问Person对象的任何方法,这时会同时执行两个SQL;当该属性为false时,调用findPersonByName方法,mybatis会执行findPersonByName映射的SQL语句,只有当访问IdCard对象时,才会执行findIdCard映射的SQL语句。 mybatis配置文件中提供了参数lazyLoadingEnabled,它是延迟加载的全局开关。

association标签里面无需再写result标签,mybatis会忽略result标签。 当选择fetchType=lazy后,执行findPersonByName的SQL语句,得到的Person对象的idCard属性为null,当访问idCard对象的属性的时候,mybatis会先执行findIdCard,根据数据库返回值构建出IdCard对象。

二、association实现原理

上面介绍了association的使用方法,方法一和方法二的原理相对来说比较简单,本文注解介绍方法三的实现原理。 当调用findPersonByName时,mybatis根据findPersonByName的入参解析映射语句和resultMap,将resultMap中的每个子节点解析为一个ResultMapping对象。ResultMapping里面有两个属性比较关键:nestedQueryId和lazy。

lazy:布尔型,当fetchType=lazy时,该属性为true;nestedQueryId:String类,和association标签里面的select属性值一样。

如果ResultMapping对象里面nestedQueryId属性不为null且lazy=true,那么表示需要使用懒加载,当需要关联对象的时候才能执行关联SQL。那么mybatis如何知道什么时候需要关联对象? 答案藏在DefaultResultSetHandler.createResultObject()方法中。mybatis执行SQL语句后,需要将SQL返回值赋值到返回对象中,在赋值之前,需要调用createResultObject()方法创建返回对象,下面看一下这个方法:

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { this.useConstructorMappings = false; final List constructorArgTypes = new ArrayList(); final List constructorArgs = new ArrayList(); //根据resultMap标签的type属性或者resultType属性创建返回对象 //这个调用构造方法直接创建对象 Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final List propertyMappings = resultMap.getPropertyResultMappings(); //遍历每个ResultMapping对象 for (ResultMapping propertyMapping : propertyMappings) { //判断是否需要懒加载 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { //调用代理工厂创建代理resultObject的代理对象 resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } this.useConstructorMappings = (resultObject != null && !constructorArgTypes.isEmpty()); // set current mapping result return resultObject; }

createResultObject()方法如果发现需要懒加载,那么便创建一个代理对象,并且将该代理对象返回给调用方。当需要访问关联SQL的时候,代理对象会检查当前访问的属性是否需要执行关联SQL,如果需要便调用映射ID执行查询。 mybatis提供了两种创建代理的方式:CGLIB和JAVASSIST,默认是JAVASSIST,可以在配置文件中使用proxyFactory修改代理方式:



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3